<?php
namespace Cola\Ext\Cache;
use Cola\Exception;

/**
 * use https://github.com/nicolasff/phpredis
 *
 */
class Redis extends Base
{
    public $config = array(
        'persistent' => true,
        'host'       => '127.0.0.1',
    	'port'       => 6379,
    	'select'	 => 0,
        'timeout'    => 3,
        'ttl'        => 0,
        'password'   => '',
        'options'    => array()
    );

    /**
     * 出现了连接错误
     *
     * @var bool
     */
    protected $connectError = false;

    /**
     * Constructor
     *
     * @param array $config
     */
    public function __construct($config = array())
    { 
        parent::__construct($config);

        $this->conn = new \Redis();

        if (empty($this->config['persistent'])) {
            $this->conn->connect($this->config['host'], $this->config['port'], $this->config['timeout']);
        } else {
            $this->conn->pconnect($this->config['host'], $this->config['port'], $this->config['timeout']);
        }

        try {
            if(!empty($this->config['password'])) {
                $this->conn->auth($this->config['password']);
            }

            foreach ($this->config['options'] as $key => $val) {
                $this->conn->setOption($key, $val);
            }

            $this->conn->select($this->config['select']);
        } catch (\RedisException $e) {
            $this->connectError = true;
            logfile('redis_error', (string)$e);
        }
    }

    /**
     * Set cache
     *
     * @param string $id
     * @param string|array $data
     * @param int $ttl
     *
     * @return boolean
     */
    public function set($id, $data, $ttl = null)
    {
        // 如果本次请求已经出现过一次错误了，那么直接返回
        if($this->connectError) {
            return false;
        }

    	if(is_array($data)){
    		$data= json_encode($data);
    	}
    	
        if (null === $ttl) {
            $ttl = $this->config['ttl'];
        }

        try {
            if (empty($ttl)) {
                return $this->conn->set($id, $data);
            } else {
                return $this->conn->setex($id, $ttl, $data);
            }
        } catch (\RedisException $e) {
    	    $this->connectError = true;
            logfile('redis_error', (string)$e);
            return false;
        }
    }

    /**
     * Get Cache Value
     * @param mixed $id
     * @return mixed
     */
    public function get($id)
    {
        // 如果本次请求已经出现过一次错误了，那么直接返回
        if($this->connectError) {
            return false;
        }

        try {
            if (is_string($id)) {
                $v = $this->conn->get($id);
                $v1 = json_decode($v, true);
                return is_array($v1) ? $v1 : $v;
            }

            return array_combine($id, $this->conn->mGet($id));
        } catch (\RedisException $e) {
            $this->connectError = true;
            logfile('redis_error', (string)$e);
            return false;
        }
    }

    /**
     * 设置hash表缓存
     * @param $table
     * @param $key
     * @param $data
     * @return bool|int
     */
    public function hset($key, $field, $data)
    {
        // 如果本次请求已经出现过一次错误了，那么直接返回
        if($this->connectError) {
            return false;
        }

        if(is_array($data)){
            $data= json_encode($data);
        }

        try {
            return $this->conn->hset($key, $field, $data);
        } catch (\RedisException $e) {
            $this->connectError = true;
            logfile('redis_error', (string)$e);
            return false;
        }
    }

    public function hget($key, $field)
    {
        // 如果本次请求已经出现过一次错误了，那么直接返回
        if($this->connectError) {
            return false;
        }

        try {
            if (is_string($key)) {
                $v = $this->conn->hget($key, $field);
                $v1 = json_decode($v, true);
                return is_array($v1) ? $v1 : $v;
            }

        } catch (\RedisException $e) {
            $this->connectError = true;
            logfile('redis_error', (string)$e);
            return false;
        }
    }
    
    /**
     * Put into Queue
     *
     */
    public function qput($queue, $value)
    {
        return $this->conn->lPush($queue, $value);
    }

    /**
     * Get from queue
     *
     * @param string $queue
     * @param int    $timeout >=0 for block, negative for non-blocking
     *
     * @return bool|string
     */
    public function qget($queue, $timeout = 0)
    {
        if (0 > $timeout) {
            return $this->conn->rPop($queue);
        } else {
            $data = $this->conn->brPop((array)$queue, $timeout);
            return isset($data[1]) ? $data[1] : false;
        }
    }

    /**
     * 加redis锁
     *
     * @param string $key 键值
     * @param  int   $ttl 锁的过期时间
     * @param  int   $keep 等待秒数
     *
     * @throws \Cola\Exception
     */
    public function lock($key, $ttl = 30, $keep = 5)
    {
        $i = $keep * 2;
        while($i--)
        {
            if($this->getLock($key, $ttl))
            {
                break;
            }

            usleep(500000);
        }

        if($i < 0)
        {
            throw new Exception("获取锁失败.key: {$key}");
        }
    }

    /**
     * 解redis锁
     * @param $key
     */
    public function unlock($key)
    {
        $this->conn->del($key);
    }

    private function getLock($key, $ttl)
    {
        $no_lock = $this->conn->setnx($key, 1);

        if($no_lock) {
            $this->conn->expire($key, $ttl);
            return true;
        } else {
            return false;
        }
    }

}